/*!
 * avangard/chat/chat_controller.js
 * © 2009 avangardonline.ru
 * @author BlooDHounD <http://www.controller.by>
 */

if ( !window.controller ) throw new Error( '"controller" not initialized' );

controller.require( 'avangard.chat' );

if ( !avangard.chat.ChatController ) {

	controller.require( 'controller.events.EventDispatcher' );
	controller.require( 'controller.utils' );

	/**
	 * класс работы с чатом
	 * @class
	 * @namespace	avangard.chat
	 * @extends		controller.events.EventDispatcher
	 * @requires	controller.Flash.sound
	 * @requires	controller.Flash.Socket
	 * @requires	controller.utils.Timer
	 * @requires	avangard.chat.Channel
	 * @requires	avangard.chat.Message
	 * @requires	avangard.chat.ChatData
	 * @author		BlooDHounD	<http://www.controller.by>
	 */
	avangard.chat.ChatController = function() {

		//--------------------------------------------------------------------------
		//
		//  Class variables
		//
		//--------------------------------------------------------------------------

		/**
		 * @private
		 */
		var	$ =			controller,
			utils =		$.utils,

			_logging =	$.isLogging(),

			_last =			'',
			_empty =		/^(\s*(to|private)\s*\[\s*([^\]]+?)\s*\])*\s*$/,
			_private_to =	/(?:private|to)\s*\[\s*([^\]]+?)\s*\]/g,
			_private =		/private\s*\[\s*([^\]]+?)\s*\]/g,
			_cmd =			/^(.*?)\s*\/\/\s*([^\s]+)\s*$/,

			_ignore =	new Object(),
			_cmds =		new Object(),

			_e1 =		'channel with id "%s" absent',
			_e2 =		'length of arguments do not match';

		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------

		/**
		 * @constructor
		 * @param	{Object}	display
		 * @param	{String}	host				хост
		 * @param	{Number}	port				порт
		 * @param	{String}	login
		 * @param	{String}	session
		 */
		var ChatController = function(display, host, port, login, session) {
			ChatControllerSuperPrototype.constructor.call( this );
			console.log(display);
			// создаём флэшку
			$.require( 'controller.Flash.Socket' );
			
			console.log(session);

			var c = this._connection = new $.Flash.Socket();

			//b.require( 'controller.Flash.sound' );
			$.require( 'avangard.chat.ChatData' );
			$.require( 'avangard.Character' );
			$.require( 'avangard.chat.Channel' );
			$.require( 'avangard.chat.ChannelHeader' );
			$.require( 'avangard.chat.Message' );

			this._data = new avangard.chat.ChatData();

			this._host =	host;
			this._port =	port;
			this._login =	login;
			this._session =	session;
			this._display =	display;

			// инитиализируем
			// пописываемся на собтия сокета
			c.addEventListener( 'connect',			this, connectHandler );
			c.addEventListener( 'close',			this, closeHandler );
			c.addEventListener( 'ioError',			this, errorConnectHandler );
			c.addEventListener( 'securityError',	this, errorConnectHandler );
			c.addEventListener( 'asyncError',		this, asyncErrorHandler );
			c.addEventListener( 'serializeError',	this, serializerErrorHandler );
			// вешаем хэнлеры на методы
			var delegate = utils.createDeferredDelegate;
			c.login =					delegate( this, this.login,					sendError );
			c.loginError =				delegate( this, this.loginError,			sendError );
			c.setChannels =				delegate( this, this.setChannels,			sendError );
			c.setChannelsError =		delegate( this, this.setChannelsError,		sendError );
			c.openChannel =				delegate( this, this.openChannel,			sendError );
			c.openChannelError =		delegate( this, this.openChannelError,		sendError );
			c.closeChannel =			delegate( this, this.closeChannel,			sendError );
			c.closeChannelError =		delegate( this, this.closeChannelError,		sendError );
			c.addUserToChannel =		delegate( this, this.addUserToChannel,		sendError );
			c.removeUserFromChannel =	delegate( this, this.removeUserFromChannel,	sendError );
			c.setChannelUsersList =		delegate( this, this.setChannelUsersList,	sendError );
			c.pong =					delegate( this, this.pong,					sendError );
			c.sendMessage =				delegate( this, this.sendMessage,			sendError );
			c.sendRawMessage =			delegate( this, this.sendRawMessage,		sendError );
			c.sendPrivateMessage =		delegate( this, this.sendPrivateMessage,	sendError );
			c.sendMessageError =		delegate( this, this.sendMessageError,		sendError );
			c.inviteToChannel =			delegate( this, this.inviteToChannel,		sendError );
			c.inviteToChannelError =	delegate( this, this.inviteToChannelError,	sendError );
			c.invitingToChannel =		delegate( this, this.invitingToChannel,		sendError );
			// конектимся
			connect.call( this );
			// таймер для пингпонга
			$.require( 'controller.utils.Timer' );
			this._timer = new utils.Timer( 2e4 );
			this._timer.addEventListener( 'timer', this, timerHandler );

		};

		$.extend( ChatController, $.events.EventDispatcher );

		var ChatControllerPrototype =		ChatController.prototype,
			ChatControllerSuperPrototype =	ChatController.superPrototype;

		//--------------------------------------------------------------------------
		//
		//  Variables
		//
		//--------------------------------------------------------------------------

		/**
		 * @private
		 * @type	{Number}
		 */
		ChatControllerPrototype._connectionError = 0;

		/**
		 * @private
		 * @type	{Boolean}
		 */
		ChatControllerPrototype._proxy = false;

		/**
		 * @private
		 * @type	{String}
		 */
		ChatControllerPrototype._host = null;

		/**
		 * @private
		 * @type	{Number}
		 */
		ChatControllerPrototype._port = null;

		/**
		 * @private
		 * @type	{String}
		 */
		ChatControllerPrototype._login = null;

		/**
		 * @private
		 * @type	{String}
		 */
		ChatControllerPrototype._session = null;

		/**
		 * @private
		 * @type	{controller.Flash.Socket}
		 */
		ChatControllerPrototype._connection = null;

		/**
		 * @private
		 * @type	{controller.utils.Timer}
		 */
		ChatControllerPrototype._timer = null;

		/**
		 * @private
		 * @type	{Boolean}
		 */
		ChatControllerPrototype._hasPong = true;

		/**
		 * @private
		 * @type	{Object}
		 */
		ChatControllerPrototype._display = null;

		/**
		 * @property
		 * @type	{avangard.chat.ChatData}
		 */
		ChatControllerPrototype._data = null;

		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------

		/**
		 * @private
		 * @this	{avangard.chat.ChatController}
		 */
		var connect = function() {
			if ( this._display ) {
				this._display.dispatcher.removeEventListener( 'login', this, connect );
				this._display.setState( 'wait' );
			}
			// делаем реконект
			utils.deferredCall(				
				this._connection,
				this._connection.connect,
				this._host,
				this._port,
				this._proxy
			);
		};

		/**
		 * @private
		 * @this	{avangard.chat.ChatController}
		 * @param	{Error}		e
		 */
		var sendError = function(e) {
			if ( _logging && window.conlose ) {
				console.error( e );
			}
		};

		/**
		 * @private
		 * @this	{avangard.chat.ChatController}
		 * @param	{Number}	channelID
		 * @param	{Array}		names
		 */
		_cmds[ '+ignore' ] = function(channelID, names) {
			var i = 0,
				l = names.length;
			for ( i=0; i<l; i++ ) {
				_ignore[ names[ i ].toUpperCase() ] = true;
			}
		};

		/**
		 * @private
		 * @this	{avangard.chat.ChatController}
		 * @param	{Number}	channelID
		 * @param	{Array}		names
		 */
		_cmds[ '-ignore' ] = function(channelID, names) {
			var i = 0,
				l = names.length;
			for ( i=0; i<l; i++ ) {
				delete _ignore[ names[ i ].toUpperCase() ];
			}
		};

		/**
		 * @private
		 * @this	{avangard.chat.ChatController}
		 */
		_cmds[ 'stop' ] = function() {
			this._connection.close();
		};

		/**
		 * @private
		 * @this	{avangard.chat.ChatController}
		 * @param	{Number}	channelID
		 */
		_cmds[ 'clear' ] = function(channelID) {
			this._data.channels.get( channelID ).logger.clear();
		};

		//--------------------------------------------------------------------------
		//
		//  Event handlers
		//
		//--------------------------------------------------------------------------

		/**
		 * @private
		 * установилось соединение с сервером
		 * @param	{controller.events.Event}	event
		 */
		var connectHandler = function(event) {
			this._connectionError = 0;
			if ( this._display ) {
				this._display.setState( 'content' );
			}
			this._timer.start();			
			this.call( 'login', this._login, this._session );
		};

		/**
		 * @private
		 * ошибка при подключении
		 * @param	{controller.events.Event}	event
		 */
		var errorConnectHandler = function(event) {
			if ( ++this._connectionError >= 4 ) {
				this._connectionError = 0;
				if ( this._display ) {
					this._display.setState( 'login' );
					this._display.dispatcher.addEventListener( 'login', this, connect );
				}
			} else {
				this._proxy = !this._proxy;
				connect.call( this );
			}
		};

		/**
		 * @private
		 * сервер закрыл соединение, или сокет не удлось открыть
		 * @param	{controller.events.Event}	event
		 */
		var closeHandler = function(event) {
			// инитилизируем чат
			if ( this._display ) {
				this._display.dispatcher.removeEventListener( 'input',			this, inputHandler );
				this._display.dispatcher.removeEventListener( 'messageSelect',	this, messageSelectHandler );
				this._display.dispatcher.removeEventListener( 'channelSelect',	this, channelSelectHandler );
				this._display.setData( null );
			}
			// повторный вход
			this._data.characterID = 0;
			this._data.characterName = null;
			this._data.channels.clear();
			this._data.channelHeaders.clear();
			this._timer.stop();
			this._hasPong = true;
			if ( this._display ) {
				this._display.setState( 'login' );
				this._display.dispatcher.addEventListener( 'login', this, connect );
			}
		};

		/**
		 * @private
		 * ошибка сериализации, лучше перезайти
		 * @param	{controller.events.Event}	event
		 */
		var serializerErrorHandler = function(event) {
			asyncErrorHandler.call( this, event );
			this._connection.close();
		};

		/**
		 * @private
		 * не очень критичная ошибка работы сокета
		 * @param	{controller.events.Event}	event
		 */
		var asyncErrorHandler = function(event) {
			sendError.call( this, event.error );
		};

		/**
		 * @private
		 */
		var timerHandler = function() {
			if ( this._hasPong ) {
				this._hasPong = false;
				this._connection.call( 'ping' );
			} else {
				this._connection.close();
			}
		};

		/**
		 * @private
		 * @param	{controller.events.Event}	event
		 */
		var inputHandler = function(event) {
			var	message = event.message;
			if ( message == _last ) return;
			var	arr =	_cmd.exec( message ),
				names =	new Array();
			if ( arr ) {
				var n = arr[ 2 ].toLowerCase();
				if ( n in _cmds ) {
					var nn = arr[ 1 ];
					if ( nn ) {
						while ( arr = _private_to.exec( nn ) ) {
							names.push( arr[ 1 ] );
						}
					}
					_cmds[ n ].call( this, event.channel, names );
					return;
				}
			}
			if ( !_empty.test( message ) ) {
				_last = message;
				var	ids =	new Array(),
					c;
				names =	new Array();
				while ( arr = _private.exec( message ) ) {
					c = this._data.channels.get( event.channel ).getCharacterByName( arr[ 1 ] );
					if ( c )	ids.push( c.id ); // резолвим ID
					else		names.push( arr[ 1 ] );
				}
				this.call( 'sendMessage', event.channel, ids, names, message );
			}
		};

		/**
		 * @private
		 * @param	{controller.events.Event}	event
		 */
		var messageSelectHandler = function(event) {
			if ( !confirm ( 'Вы дейсвительно хотите пожаловаться на сообщение?' ) ) return; // TODO: lang, Window
			this.call( 'sendComplaint', event.channel, event.message.id, event.message.sendTime / 1e3 );
		};

		/**
		 * @private
		 * @param	{controller.events.Event}	event
		 */
		var channelSelectHandler = function(event) {
			this.call( 'openChannel', event.channel );
		};

		//--------------------------------------------------------------------------
		//
		//  Command handlers
		//
		//--------------------------------------------------------------------------

		/**
		 * @method
		 * создание экрана чата
		 * @param	{Number}	id
		 * @param	{String}	name
		 */
		ChatControllerPrototype.login = function(id, name) {
			// инитилизируем чат
			if ( this._display ) {
				this._data.characterID = id;
				this._data.characterName = name;
				this._display.setData( this._data );
				this._display.dispatcher.addEventListener( 'input',			this, inputHandler );
				this._display.dispatcher.addEventListener( 'messageSelect',	this, messageSelectHandler );
				this._display.dispatcher.addEventListener( 'channelSelect',	this, channelSelectHandler );
			}
			this.openChannel( -1, 'private', 100 );
		};

		/**
		 * @method
		 * ошибка авторизации :( плохо
		 * @param	{Number}	code
		 */
		ChatControllerPrototype.loginError = function(code) {
			// TODO: отображать ошибку
		};

		/**
		 * @method
		 * список существующих каналов
		 * @param	{Array}	ids
		 * @param	{Array}	names
		 * @param	{Array}	types
		 */
		ChatControllerPrototype.setChannels = function(ids, names, types) {
			var	l = ids.length;
			if ( names.length != l || types.length != l ) throw new Error( _e2 );
			this._data.channelHeaders.clear();
			var i;
			for ( i=0; i<l; i++ ) {
				this._data.channelHeaders.add( new avangard.chat.ChannelHeader( ids[i], types[i], names[i] ) );
			}
		};

		/**
		 * @method
		 * ошибка получения списка существующих каналов
		 * @param	{Number}	code
		 */
		ChatControllerPrototype.setChannelsError = function(code) {
		};

		/**
		 * @method
		 * открылся канал
		 * @param	{Number}	id
		 * @param	{String}	name
		 * @param	{Number}	type
		 */
		ChatControllerPrototype.openChannel = function(id, name, type) {
			if ( this._data.channels.has( id ) ) this.closeChannel( id );
			this._data.channels.add(
				id,
				new avangard.chat.Channel( id, type, name )
			);
		};

		/**
		 * @method
		 * произошла ошибка открытия канала
		 * @param	{Number}	code
		 */
		ChatControllerPrototype.openChannelError = function(code) {
		};

		/**
		 * @method
		 * зактие канала
		 * @param	{Number}	id
		 */
		ChatControllerPrototype.closeChannel = function(id) {
			if (this._data.channels.has(id))
			{
				var channel = this._data.channels.remove( id );
				channel.dispose();
			}
		};

		/**
		 * ошибка закрытия канала
		 * @param	{Number}	code
		 */
		ChatControllerPrototype.closeChannelError = function(code) {
		};

		/**
		 * @method
		 * появился новый пользователь
		 * @param	{Number}	channelID
		 * @param	{Number}	id
		 * @param	{String}	name
		 * @param	{Number}	race
		 * @throws	{Error}					channel with id absent
		 */
		ChatControllerPrototype.addUserToChannel = function(channelID, id, name, sex, race, level, clanID, clanName, propensity) {
			var channel = this._data.channels.get( channelID );
			if ( !channel ) throw new Error( _e1.replace( '%s', channelID ) );
			var	character = channel.getCharacterByID( id ),
				h = !character;
			if ( h ) {
				character = new avangard.Character( id, name );
			}
			character.sex =			sex;
			character.race =		race;
			character.level =		level;
			character.clanID =		clanID;
			character.clanName =	clanName;
			character.propensity =	propensity;
			if ( h ) {
				channel.addCharacter( character );
			} else {
				character.update();
			}
		};

		/**
		 * @method
		 * пользователь покинул канал
		 * @param	{Number}	channelID
		 * @param	{Number}	id
		 * @throws	{Error}					channel with id absent
		 */
		ChatControllerPrototype.removeUserFromChannel = function(channelID, id) {
			var channel = this._data.channels.get( channelID );
			if ( !channel ) throw new Error( _e1.replace( '%s', channelID ) );
			var character = channel.getCharacterByID( id );
			channel.removeCharacter( character );
			character.dispose();
		};

		/**
		 * @method
		 * список пользователей на канале
		 * @param	{Number}	channelID
		 * @param	{Array}		ids
		 * @param	{Array}		names
		 * @param	{Array}		races
		 * @throws	{Error}					length of arguments do not match
		 */
		ChatControllerPrototype.setChannelUsersList = function(channelID, ids, names, sexes, races, levels, clanIDs, clanNames, propensities) {
			var channel = this._data.channels.get( channelID );
			if ( !channel ) throw new Error( _e1.replace( '%s', channelID ) );
			var	characters = channel.getCharacters(),
				i, id,
				l = ids.length,
				hash = new Object();
			if (
				names.length != l ||
				sexes.length != l ||
				races.length != l ||
				levels.length != l ||
				clanIDs.length != l ||
				clanNames.length != l ||
				propensities.length != l
			) {
				throw new Error( _e2 );
			}
			for ( i=0; i<l; i++ ) {
				id = ids[ i ];
				hash[ id ] = true;
				this.addUserToChannel(
					channelID,
					ids[ i ],
					names[ i ],
					sexes[ i ],
					races[ i ],
					levels[ i ],
					clanIDs[ i ],
					clanNames[ i ],
					propensities[ i ]
				);
			}
			l = characters.length;
			for ( i=0; i<l; i++ ) {
				id = characters[ i ].id;
				if ( !( id in hash ) ) {
					this.removeUserFromChannel( channelID, id );
				}
			}
		};

		/**
		 * @method
		 */
		ChatControllerPrototype.pong = function() {
			this._hasPong = true;
		};

		/**
		 * @private
		 * @param	{Number}	channelID
		 * @param	{Number}	id
		 * @param	{Number}	type
		 * @param	{String}	senderName
		 * @param	{String}	message
		 * @param	{Number}	time
		 */
		var sendMessage = function(channelID, id, type, senderName, message, time) {
			var channel = this._data.channels.get( channelID );
			if ( !channel ) throw new Error( _e1.replace( '%s', channelID ) );
			channel.logger.add(
				new avangard.chat.Message(
					id,
					type,
					senderName,
					message,
					time * 1e3
				)
			);
		};

		/**
		 * @method
		 * появилосьновое сообщение в канале
		 * @param	{Number}	channelID
		 * @param	{Number}	id
		 * @param	{String}	senderName
		 * @param	{String}	message
		 * @param	{Number}	time
		 * @throws	{Error}					channel with id absent
		 */
		ChatControllerPrototype.sendMessage = function(channelID, id, senderName, message, time) {
			sendMessage.call( this, channelID, id, 0, senderName, message, time );
		};

		/**
		 * @method
		 * появилосьновое сообщение в канале
		 * @param	{Number}	channelID
		 * @param	{Number}	id
		 * @param	{String}	message
		 * @param	{Number}	time
		 * @throws	{Error}					channel with id absent
		 */
		ChatControllerPrototype.sendRawMessage = function(channelID, id, message, time) {
			//if ( senderName.toUpperCase() in _ignore ) return;
			sendMessage.call( this, channelID, id, 1, null, message, time );
		};

		/**
		 * @method
		 * появилосьновое сообщение в канале
		 * @param	{Number}	id
		 * @param	{String}	senderName
		 * @param	{String}	message
		 * @param	{Number}	time
		 * @throws	{Error}					channel with id absent
		 */
		ChatControllerPrototype.sendPrivateMessage = function(id, senderName, message, time) {
			if ( senderName.toUpperCase() in _ignore ) return;
			//$.Flash.sound.loadAndPlay( '/mp3/test.mp3', null, null, null, true );
			sendMessage.call( this, -1, id, 2, senderName, message, time );
			if ( senderName.toLowerCase() == this._data.characterName.toLowerCase() ) {
				this._display.setChannel( this._data.channels.get( -1 ) )
			}
		};

		/**
		 * @method
		 * @param	{Number}	code
		 */
		ChatControllerPrototype.sendMessageError = function(code) {
		};

		ChatControllerPrototype.inviteToChannel = function(channelID, userID) {
		};

		/**
		 * @method
		 * @param	{Number}	code
		 */
		ChatControllerPrototype.inviteToChannelError = function(code) {
		};

		ChatControllerPrototype.invitingToChannel = function(channelID, userID) {
		};

		//--------------------------------------------------------------------------
		//
		//  Methods
		//
		//--------------------------------------------------------------------------

		/**
		 * @method
		 * @return		{Object}
		 */
		ChatControllerPrototype.getDisplay = function() {
			return this._display;
		};

		/**
		 * @method
		 * @override
		 */
		ChatControllerPrototype.dispose = function() {
			this._connection.close();
			this._connection.dispose();
			this._connection = null;
			this._timer.dispose();
			this._timer = null;
			this._display = null;
			this._data.dispose();
			this._data = null;
			ChatControllerSuperPrototype.dispose.call( this );
		};

		/**
		 * @method
		 * @param	{String}	methodName
		 * @throws	{Error}					connection absent
		 */
		ChatControllerPrototype.call = function(methodName) {
			if ( !this._connection.isConnected() ) throw new Error( 'connection absent' );
			this._timer.reset();
			this._timer.start();
			this._connection.call.apply( this._connection, arguments );
		};

		/**
		 * @method
		 * @return	{String}
		 */
		ChatControllerPrototype.toString = function() {
			return '[ChatController object]';
		};

		//--------------------------------------------------------------------------
		//
		//  Class methods
		//
		//--------------------------------------------------------------------------

		/**
		 * @static
		 * @method
		 * @return	{String}
		 */
		ChatController.toString = function() {
			return '[class ChatController]';
		};

		return ChatController;

	}();

}
